﻿using MQTTnet;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using log4net;

namespace Plugin
{
    public class MqttClient
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(MqttClient));

        private IMqttClient? mqttClient = null;

        private MqttClientConnectResult? connectResult = null;

        public bool IsConnected => (mqttClient?.IsConnected).GetValueOrDefault();

        public string DeviceId { get; set; } = string.Empty;

        public static string mqttTopic { get; set; } = string.Empty;


        public async Task<bool> ConnectAsync(FileInfo x509_zipfile)
        {
            if (x509_zipfile.Exists)
            {
                var file = System.IO.Compression.ZipFile.OpenRead(x509_zipfile.FullName);
                byte[] getzipfile(string filename)
                {
                    byte[] unzippedArray = null;
                    using (var unzippedEntryStream = file.GetEntry(filename).Open())
                    {
                        using (var ms = new MemoryStream())
                        {
                            unzippedEntryStream.CopyTo(ms);
                            unzippedArray = ms.ToArray();
                        }
                    }
                    return unzippedArray;
                };
                X509Certificate2 ca ;
                X509Certificate2 client;
                try
                {
                    ca = X509Certificate2.CreateFromPem(System.Text.Encoding.Default.GetChars(getzipfile("ca.crt")));
                    client = X509Certificate2.CreateFromPem(System.Text.Encoding.Default.GetChars(getzipfile("client.crt")), System.Text.Encoding.Default.GetChars(getzipfile("client.key")));
                }
                catch (Exception ex)
                {
                    throw new Exception("从Zip中加载证书文件错误", ex);
                }
                if (ca!=null && client!=null)
                {
                    return await ConnectAsync(ca, client);
                }
                else
                {
                    throw new Exception("证书无效");
                }
            }
            else
            {
                throw new Exception("指定的ZIP文件不存在");
            }
        }


        public async Task<bool> ConnectAsync(X509Certificate2 ca, X509Certificate2 client, Uri uri=null)
        {
            var options = new MqttClientOptionsBuilder()
                .WithTlsOptions(
                    new MqttClientTlsOptions ()
                    {
                        UseTls = true,
                        SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
                         AllowUntrustedCertificates = true,
                          
                        CertificateValidationHandler = (certContext) =>
                        {
                            var chain = certContext.Chain;
                            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
                            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
                            chain.ChainPolicy.VerificationTime = DateTime.UtcNow;
                            chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
                            chain.ChainPolicy.CustomTrustStore.Add(ca);
                            chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                            var x5092 = new X509Certificate2(certContext.Certificate);
                            return chain.Build(x5092);
                        }, 
                         CertificateSelectionHandler = (certContext) =>
                         {
                             return client;
                         }
                        
                   
                    });
            if (uri != null)
            {
                options.WithTcpServer(uri.Host, uri.Port);
            }
            else
            {
                if (Uri.TryCreate(client.GetNameInfo(X509NameType.UrlName, false), UriKind.RelativeOrAbsolute, out Uri _uri))
                {
                    options.WithTcpServer(_uri.Host, _uri.Port);
                }
                else
                {
                    var host = client.GetNameInfo(X509NameType.DnsName, false);
                    options.WithTcpServer(host, 8883);
                }
            }
       
            return await ConnectAsync(options.Build());
        }
     
 
        public Task<bool> ConnectAsync(Uri uri, string accesstoken) => ConnectAsync(uri, accesstoken, null);

       
        public async Task<bool> ConnectAsync(Uri uri, string username, string password)
        {
            if (uri == null) throw new ArgumentNullException("url");
            var clientOptions = new MqttClientOptionsBuilder()
                   .WithClientId(uri.ToString() + Guid.NewGuid().ToString())
                   .WithTcpServer(uri.Host, uri.Port)
                   .WithCredentials(username, password)
                   .Build();
           return  await  ConnectAsync(clientOptions);
        }

   
        public async Task<bool> ConnectAsync(MqttClientOptions clientOptions, CancellationToken cancellationToken=default)
        {
            bool initok = false;
            try
            {
                var factory = new MqttClientFactory();
                mqttClient = factory.CreateMqttClient( );
                mqttClient.ConnectedAsync += ConnectedAsyncHandle;
                mqttClient.DisconnectedAsync += DisconnectedAsyncHandle;
                mqttClient.ApplicationMessageReceivedAsync +=  ApplicationMessageReceivedAsyncHandle;        
                try
                {
                    connectResult = await mqttClient.ConnectAsync(clientOptions, cancellationToken);
                    if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode == MqttClientConnectResultCode.Success))
                    {
                        log.Info(connectResult);
                        return true;
                    }
                }
                catch (Exception exception)
                {
                    log.Error("CONNECTING FAILED", exception);
                }
                log.Info("WAITING FOR APPLICATION MESSAGES");
            }
            catch (Exception exception)
            {
                log.Error("CONNECTING FAILED", exception);
            }
            return initok;
        }

        public async Task<bool> ConnectAsync(string mqttIP, int mqttPort, string mqttUser,  string mqttPwd, string clientId)
        {
            bool initok = false;
            try
            {
                // Create a MQTT client factory
                var factory = new MqttClientFactory();

                // Create a MQTT client instance
                mqttClient = factory.CreateMqttClient();
                if (mqttClient != null)
                {
                    // Create MQTT client options
                    var options = new MqttClientOptionsBuilder()
                        .WithTcpServer(mqttIP, mqttPort) // MQTT broker address and port
                        .WithCredentials(mqttUser, mqttPwd) // Set username and password
                        .WithClientId(string.IsNullOrEmpty(clientId) ? Guid.NewGuid().ToString() : clientId)
                        .WithProtocolVersion(MqttProtocolVersion.V500)
                        .WithCleanSession(true)
                        .WithCleanStart(true)
                        .WithKeepAlivePeriod(TimeSpan.FromSeconds(60))
                        .WithTimeout(TimeSpan.FromSeconds(600))
                        //.WithTlsOptions(
                        //    o =>
                        //    {
                        //        // The used public broker sometimes has invalid certificates. This sample accepts all
                        //        // certificates. This should not be used in live environments.
                        //        o.WithCertificateValidationHandler(_ => true);
                        //        // The default value is determined by the OS. Set manually to force version.
                        //        o.WithSslProtocols(SslProtocols.Tls12);
                        //    })
                        .Build();

                    mqttClient.ConnectedAsync += ConnectedAsyncHandle;

                    mqttClient.DisconnectedAsync += DisconnectedAsyncHandle;

                    mqttClient.ApplicationMessageReceivedAsync += ApplicationMessageReceivedAsyncHandle;

                    connectResult = await mqttClient.ConnectAsync(options, CancellationToken.None);
                    if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode == MqttClientConnectResultCode.Success))
                    {
                        log.Info(connectResult);
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message);
            }
            return initok;
        }

        private Task ApplicationMessageReceivedAsyncHandle( MqttApplicationMessageReceivedEventArgs arg)
        {
            log.Info($"Message received on topic: {arg.ApplicationMessage.Topic}.");
            log.Info($"Payload: {Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}");
            return Task.CompletedTask;
        }

        private Task DisconnectedAsyncHandle(MqttClientDisconnectedEventArgs arg)
        {
            log.Info("Disconnected from EMQX.");
            return Task.CompletedTask;
        }

        private async Task ConnectedAsyncHandle(MqttClientConnectedEventArgs arg)
        {
            log.Info("Connected to EMQX.");
            try
            {
                await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(mqttTopic).Build());
                log.Info("Subscribe a topic..." + mqttTopic);
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

        public async Task SubscribeAsync(string mqttTopic)
        {
            try
            {
                if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode == MqttClientConnectResultCode.Success))
                {
                    await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(mqttTopic).Build());
                    log.Info("Subscribe a topic..." + mqttTopic);
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

        public async Task ReconnectAsync()
        {
            try
            {
                if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode != MqttClientConnectResultCode.Success))
                {
                    await mqttClient.ReconnectAsync();
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

        public async Task DisconnectAsync()
        {
            try
            {
                if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode == MqttClientConnectResultCode.Success))
                {
                    await mqttClient.DisconnectAsync();
                    if (mqttClient != null)
                    {
                        mqttClient.Dispose();
                        mqttClient = null;
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

        public async Task UnsubscribeAsync()
        {
            try
            {
                if ((mqttClient != null) && (connectResult != null) && (connectResult.ResultCode == MqttClientConnectResultCode.Success))
                {
                    await MqttClientExtensions.UnsubscribeAsync(mqttClient, mqttTopic, CancellationToken.None);//退订
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

        public async Task PublishAsync(string mqttTopic, string payload)
        {
            try
            {
                if (mqttClient != null)
                {
                    // 发布内容
                    var message = new MqttApplicationMessageBuilder()
                        .WithTopic(mqttTopic)// 主题
                        .WithPayload(payload)// 信息
                        .WithRetainFlag(true)// 保留
                        .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
                        .Build();
                    MqttClientPublishResult publishResult = await mqttClient.PublishAsync(message, CancellationToken.None);// 发送
                    if ((publishResult != null) && (publishResult.IsSuccess))
                    {
                        log.Info(publishResult);
                    }
                    await Task.Delay(1000); // Wait for 1 second
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.ToString());
            }
        }

    }
    
}

